package es.esi.gemde.network;

import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EPackage;

import com.tecnalia.registry.client.EclipseRegistryClient;
import com.tecnalia.registry.client.events.EclipseRegistryEvent;
import com.tecnalia.registry.resource.Resource;
import com.tecnalia.registry.resource.gemde.ResourceConstraint;
import com.tecnalia.registry.resource.gemde.ResourceInputOutput;
import com.tecnalia.registry.resource.gemde.ResourceTransformation;
import com.tecnalia.registry.resource.gemde.ResourceValidation;
import com.tecnalia.util.eclipse.XEclipse;
import com.tecnalia.util.java.XUtil;

import es.esi.gemde.modeltransformator.ModelTransformatorPlugin;
import es.esi.gemde.modeltransformator.service.ITransformation;
import es.esi.gemde.modeltransformator.service.TransformationFactory;
import es.esi.gemde.modelvalidator.ModelValidatorPlugin;
import es.esi.gemde.modelvalidator.service.BatchValidationFactory;
import es.esi.gemde.modelvalidator.service.IBatchValidation;
import es.esi.gemde.modelvalidator.service.IModelValidatorConstraint;
import es.esi.gemde.modelvalidator.service.OCLConstraintFactory;

/**
 * This class is the equivalent of one observer, listener,handler, adapter reacting changes in the local Registry.
 * <p>When a resource is updated in the local registry, a notification is sent to all observers. This class will handle just <strong>GEMDE</strong> like resources. </p>
 *<p>See <a href="http://en.wikipedia.org/wiki/Observer_pattern" target="_blank">http://en.wikipedia.org/wiki/Observer_pattern</a></p>
 * @see EclipseRegistryClient
 * @see EclipseRegistryEvent
 */
public class GemdeRegistryObserver implements Observer {
	
	private Logger logger;
	
	public GemdeRegistryObserver(){
		super();
		logger=Logger.getLogger(this.getClass().getName());
		try {
			//check if registry is alive
			EclipseRegistryClient eclipseRegistryClient=null;
			Map<String,Resource> mapRegistryResources=null;
			try{
				eclipseRegistryClient=EclipseRegistryClient.getInstance();
				mapRegistryResources=eclipseRegistryClient.getLocalResources();
			}catch(Exception e){
					throw new Exception("Registry client is not available yet. GEMDE cannot load resources in local repository.",e);
			}
			//load GEMDE resources from local registry into GEMDE engines
			for(String id:mapRegistryResources.keySet()){
				Resource resource=mapRegistryResources.get(id);
				if (resource instanceof ResourceTransformation)	addTransformationToGemde((ResourceTransformation) resource);
				else if (resource instanceof ResourceValidation)	addValidationToGemde((ResourceValidation) resource);
			}
		} catch (Exception e) {
			logger.severe(e.getMessage());
			logger.log(Level.FINEST,e.getMessage(),e);
		}	
		
	}
	
	/**
	 * Update GEMDE when a resource has changed in the registry
	 * @param observable the observable object. 
	 * @param object this object should be  an EclipseRegistryEvent type containing the event, and the resource that has changed.
	 */
	@Override
	public void update(Observable observable, Object object) {
		try {
			EclipseRegistryEvent event=(EclipseRegistryEvent) object;
			if (event.resource instanceof ResourceTransformation){
				ResourceTransformation resourceTrasformation=(ResourceTransformation) event.resource;
				switch(event.eventType){
					case EclipseRegistryEvent.EVENT_DOWNLOAD:
						addTransformationToGemde(resourceTrasformation);
						break;
					case EclipseRegistryEvent.EVENT_DELETE:
						ModelTransformatorPlugin.getService().removeTransformation(resourceTrasformation.name);
						break;
					default:
						break;
				}
			}
			if (event.resource instanceof ResourceValidation ){
				ResourceValidation resourceValidation=(ResourceValidation) event.resource;
				switch(event.eventType){
					case EclipseRegistryEvent.EVENT_DOWNLOAD:
						addValidationToGemde(resourceValidation);
						break;
					case EclipseRegistryEvent.EVENT_DELETE:
						removeValidationAndConstraintsFromGemde(resourceValidation);
						break;
					default:
						break;
				}
			}
		} catch (Exception e) {
			String title="Gemde Network System";
			String message="Error when processing network operation. "+e.getMessage();
			logger.log(Level.SEVERE, title+". "+message,e);
			XEclipse.messageBoxError(title, message);
		}
	}
	

	private void addTransformationToGemde(ResourceTransformation resourceTrasformation) throws Exception{
		//if old version exists, first of all, remove the loaded transformation
		ModelTransformatorPlugin.getService().removeTransformation(resourceTrasformation.name);
		//add the new transformation
		String name=resourceTrasformation.name;
		String engine=resourceTrasformation.engine;
		String description=resourceTrasformation.description;
		IFolder folder=EclipseRegistryClient.getInstance().getLocalFolder(resourceTrasformation.id);
		List<URI> files = new ArrayList<URI>();
		HashMap<String, EClass > inputs = new HashMap< String, EClass >();
		IResource[] iResources=folder.members();
		for(IResource res:iResources){
			if (res instanceof IFile){
				IFile iFile=(IFile) res;
				if (iFile.getName().endsWith(".json")==false){
					files.add(iFile.getLocationURI());
				}
			}
		}
		List<String> idInputs=resourceTrasformation.inputs;
		for (int r=0;r<idInputs.size();r++){
			ResourceInputOutput input=(ResourceInputOutput) EclipseRegistryClient.getInstance().get(idInputs.get(r));
			if (XUtil.isEmpty(name)) name="input_"+Integer.toString(r);
			EClass i1=this.createInputElement(input.eClass, input.ePackage );
			inputs.put(name, i1);
		}
		ITransformation t = TransformationFactory.createNetworkedTransformation(name, engine, files, description, inputs);
		ModelTransformatorPlugin.getService().addTransformation(t);
	}

	private void addValidationToGemde(ResourceValidation resourceValidation) throws Exception{
		//if old version exists, first of all, remove the loaded validation
		removeValidationAndConstraintsFromGemde(resourceValidation);
		//add the new transformation
		String name=resourceValidation.name;
		List<IModelValidatorConstraint>  listGemdeConstraints=new ArrayList<IModelValidatorConstraint>(); 
		List<String> constraints=resourceValidation.constraints;
		OCLConstraintFactory oclConstraintFactory=new OCLConstraintFactory();
		
		for (int r=0;r<constraints.size();r++){
			ResourceConstraint constraint=(ResourceConstraint) EclipseRegistryClient.getInstance().get(constraints.get(r));
			String cname=URLDecoder.decode(constraint.name,"UTF-8");
			String ccategory=URLDecoder.decode(constraint.category,"UTF-8");
			String cbody=URLDecoder.decode(constraint.body,"UTF-8");
			int cseverity=Integer.valueOf(constraint.severity);
			IModelValidatorConstraint gemdeConstraint=oclConstraintFactory.createNetworkedConstraint(cname, ccategory,cseverity, cbody);
			listGemdeConstraints.add(gemdeConstraint);
		}
		//register constraints
		for(IModelValidatorConstraint gemdeConstraint:listGemdeConstraints) ModelValidatorPlugin.getServer().addConstraint(gemdeConstraint);
		
		//create the validation
		IBatchValidation ibValidation=BatchValidationFactory.createNetworkedBatchValidation(name, listGemdeConstraints, listGemdeConstraints.get(0).getApplicationMetamodel());

		ModelValidatorPlugin.getServer().addBatchValidation(ibValidation);
	}
	
	private void removeValidationAndConstraintsFromGemde(ResourceValidation resourceValidation) throws Exception {
		IBatchValidation val = ModelValidatorPlugin.getServer().getBatchValidation(resourceValidation.name);
		if (val != null) {
			ModelValidatorPlugin.getServer().removeBatchValidation(resourceValidation.name);
			for (IModelValidatorConstraint con : val.getSelectedConstraints()) {
				ModelValidatorPlugin.getServer().removeConstraint(con.getName(), con.getCategory());
			}
		}
	}
	
	/**
	 * Create the appropriate EClass before inserting the transformation into GEMDE
	 * @param eClassName
	 * @param ePackageName
	 * @return
	 */
	//TODO Angel to Adrian: this method may be moved to GEMDE core package
	private EClass createInputElement(String eClassName,String ePackageName){
		EPackage p = null;
		/*try{
			Class a=Class.forName("org.eclipse.uml2.uml.Model");
		}catch(Exception e){
			;
		}*/
		String shortEClass = eClassName.lastIndexOf('.') == -1 ? eClassName : eClassName.substring(eClassName.lastIndexOf('.')+1);
		if (ePackageName != null && !ePackageName.equals("")) {
			p = EPackage.Registry.INSTANCE.getEPackage(ePackageName);
		}
		
		if (p == null) {
			// Try to guess the package of the eclass
			for (Object o : EPackage.Registry.INSTANCE.values()) {
				if (o != null && o instanceof EPackage) {
					EClassifier c = ((EPackage)o).getEClassifier(shortEClass);
					if (c != null && c instanceof EClass && ((EClass)c).getInstanceTypeName().equals(eClassName)) {
						return ((EClass) c);
					}
				}
			}
		}
		else {
			EClass c = (EClass) p.getEClassifier(shortEClass);
			return c;
		}
		return null;
	}
	
}
